package org.zjx.core.nodes;

import lombok.Getter;
import org.zjx.core.DecisionContext;
import org.zjx.core.DecisionNode;
import org.zjx.core.GameAction;
import org.zjx.exception.DecisionException;

import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 并行执行节点
 *
 * 能够并行执行多个子决策节点，使用线程池管理并发任务，
 * 支持超时控制，并具备任务取消机制。
 */
@Getter
public class ParallelNode implements DecisionNode {
    // 线程池执行器，用于并行执行子节点
    private final ExecutorService executor;

    // 子节点列表，使用不可变集合保证线程安全
    private final List<DecisionNode> branches;

    // 超时时间(毫秒)，防止任务长时间阻塞
    private final long timeoutMillis;

    /**
     * 构造函数(使用默认参数)
     *
     * @param branches 要并行执行的子节点列表
     *
     * 默认参数：
     * - timeoutMillis: 1000毫秒
     * - poolSize: CPU核心数
     * - queueCapacity: 1000
     */
    public ParallelNode(List<DecisionNode> branches) {
        this(branches, 1000, Runtime.getRuntime().availableProcessors(), 1000);
    }

    /**
     * 完整参数构造函数
     *
     * @param branches 子节点列表
     * @param timeoutMillis 超时时间(毫秒)
     * @param poolSize 线程池大小
     * @param queueCapacity 任务队列容量
     */
    public ParallelNode(List<DecisionNode> branches, long timeoutMillis,
                        int poolSize, int queueCapacity) {
        // 创建子节点列表的不可变副本，保证线程安全
        this.branches = List.copyOf(branches);
        this.timeoutMillis = timeoutMillis;

        // 初始化线程池
        this.executor = new ThreadPoolExecutor(
                poolSize, poolSize,  // 核心线程数=最大线程数(固定大小线程池)
                0L, TimeUnit.MILLISECONDS,  // 空闲线程立即终止
                new LinkedBlockingQueue<>(queueCapacity),  // 有界任务队列
                new ParallelNodeThreadFactory(),  // 自定义线程工厂
                new ThreadPoolExecutor.CallerRunsPolicy()  // 饱和策略
        );
    }

    /**
     * 执行决策逻辑(并行版本)
     *
     * @param context 决策上下文
     * @return 第一个返回非null结果的子节点结果，如果没有则返回null
     *
     * 执行流程：
     * 1. 提交所有子节点任务到线程池
     * 2. 等待任务完成或超时
     * 3. 一旦有任务返回非null结果，取消其他任务
     * 4. 处理异常情况
     */
    @Override
    public GameAction decide(DecisionContext context) {
        // 提交所有子节点任务并获取Future列表
        List<Future<GameAction>> futures = branches.stream()
                .map(node -> executor.submit(() -> node.decide(context)))
                .toList();


        try {
            // 使用CompletableFuture实现"第一个非null结果"逻辑
            CompletableFuture<GameAction> resultFuture = new CompletableFuture<>();

            futures.forEach(future ->
                    executor.execute(() -> {
                        try {
                            GameAction action = future.get(timeoutMillis, TimeUnit.MILLISECONDS);
                            if (action != null) {
                                resultFuture.complete(action); // 第一个有效动作完成future
                            }
                        } catch (Exception e) {
                            future.cancel(true);
                        }
                    })
            );

            // 等待第一个结果或超时
            return resultFuture.get(timeoutMillis, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            cancelAll(futures);
            return null;
        } catch (Exception e) {
            cancelAll(futures);
            throw new DecisionException("Parallel execution failed", e);
        }
    }

    /**
     * 取消所有未完成的任务
     *
     * @param futures Future对象列表
     */
    private void cancelAll(List<Future<GameAction>> futures) {
        // 遍历所有Future对象
        futures.forEach(f -> {
            if (!f.isDone()) {
                // 取消未完成的任务(中断正在执行的任务)
                f.cancel(true);
            }
        });
    }

    /**
     * 自定义线程工厂
     *
     * 为线程池创建具有统一命名规范的线程，
     * 并设置为守护线程(不会阻止JVM退出)。
     */
    private static class ParallelNodeThreadFactory implements ThreadFactory {
        // 线程计数器
        private final AtomicInteger counter = new AtomicInteger();

        /**
         * 创建新线程
         *
         * @param r 要执行的任务
         * @return 配置好的新线程
         */
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            // 设置线程名称(便于调试)
            t.setName("ParallelNode-worker-" + counter.getAndIncrement());
            // 设置为守护线程
            t.setDaemon(true);
            return t;
        }
    }
}